import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.HashMap;
import java.util.logging.Logger;

public class ZKListenerDataSource implements DataSource {
    //新建zk Client
    private final CuratorFramework configZKClient;

    //配置信息表
    private HashMap<String,String> configMap = new HashMap<String, String>();

    //代理连接池
    private volatile ComboPooledDataSource comboPooledDataSource;

    private ZKListenerDataSource(CuratorFramework curatorFramework){
        this.configZKClient = curatorFramework;
        if (!configZKClient.getState().equals(CuratorFrameworkState.STARTED)) {
            configZKClient.start();
        }
        try {
            getZKConfig();
            setDataSource();
            configListener();
        }catch (Exception e){
            throw new IllegalStateException("启动动态代理数据源失败！", e);
        }
    }

    //获取当前的数据库配置信息
    private void getZKConfig () throws Exception{
        String user = new String(configZKClient.getData().forPath("/CONFIG/user"));
        String password = new String(configZKClient.getData().forPath("/CONFIG/password"));
        String dbUrl = new String(configZKClient.getData().forPath("/CONFIG/dbUrl"));
        String driverClassName = new String(configZKClient.getData().forPath("/CONFIG/driverClassName"));
        configMap.put("user",user);
        configMap.put("password",password);
        configMap.put("dbUrl",dbUrl);
        configMap.put("driverClassName",driverClassName);
    }
    //创建数据源
    public static ZKListenerDataSource create (String zkAddress) throws Exception{
        return new ZKListenerDataSource(CuratorFrameworkFactory.newClient(zkAddress,new ExponentialBackoffRetry(1000, 10)));
    }
    //配置数据源
    private void setDataSource () throws PropertyVetoException {
        ComboPooledDataSource dataSource =  new ComboPooledDataSource();
        dataSource.setUser(configMap.get("user"));
        dataSource.setPassword(configMap.get("password"));
        dataSource.setJdbcUrl(configMap.get("dbUrl"));
        dataSource.setDriverClass(configMap.get("driverClassName"));

        this.comboPooledDataSource = dataSource;
    }

    //监听config变化
    private void configListener () throws Exception{
        PathChildrenCache childrenCache = new PathChildrenCache(configZKClient,"/CONFIG",true);
        childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
            public void childEvent(CuratorFramework ient, PathChildrenCacheEvent event) throws Exception {
                if(!event.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
                    return;
                }
                System.out.println("配置信息改变:" + new String(event.getData().getData()));
                getZKConfig();
                final ComboPooledDataSource currentDatasource = ZKListenerDataSource.this.comboPooledDataSource;
                setDataSource();
                if (currentDatasource != null) {
                    currentDatasource.close();
                }
            }
        });
        childrenCache.start();
        System.out.println("监听完成");
    }

    public Connection getConnection() throws SQLException {
        return comboPooledDataSource.getConnection();
    }

    public Connection getConnection(String username, String password) throws SQLException {
        return comboPooledDataSource.getConnection(username, password);
    }

    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    public void setLoginTimeout(int seconds) throws SQLException {

    }

    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }
}
